(*  :Title:	Analog Signal Processing Analysis  *)

(*  :Authors:	Brian Evans, James McClellan  *)

(*
    :Summary:	Perform basic analysis on a one-dimensional continuous-time
		functions, including plotting f(t) and graphing the poles
		and zeroes of F(s).
 *)

(*  :Context:	SignalProcessing`Analog`ASPAnalyze`  *)

(*  :PackageVersion:  2.7  *)

(*
    :Copyright:	Copyright 1990-1991 by Brian L. Evans
		Georgia Tech Research Corporation

	Permission to use, copy, modify, and distribute this software
	and its documentation for any purpose and without fee is
	hereby granted, provided that the above copyright notice
	appear in all copies and that both that copyright notice and
	this permission notice appear in supporting documentation,
	and that the name of the Georgia Tech Research Corporation,
	Georgia Tech, or Georgia Institute of Technology not be used
	in advertising or publicity pertaining to distribution of the
	software without specific, written prior permission.  Georgia
	Tech makes no representations about the suitability of this
	software for any purpose.  It is provided "as is" without
	express or implied warranty.
 *)

(*  :History:	began -- March, 1990 (adapted from "ZTransform.m")	*)

(*  :Keywords:	signal processing, Laplace transform, pole-zero plot *)

(*  :Source:	*)

(*  :Warning:	*)

(*  :Mathematica Version:  1.2 or 2.0  *)

(*  :Limitation:  *)

(*  :Functions: ASPAnalyze  *)



(*  B E G I N     P A C K A G E  *)

BeginPackage[ "SignalProcessing`Analog`ASPAnalyze`",
	      "SignalProcessing`Analog`Fourier`",
	      "SignalProcessing`Analog`LaPlace`",
	      "SignalProcessing`Analog`LSupport`",
	      "SignalProcessing`Support`TransSupport`",
	      "SignalProcessing`Support`ROC`",
	      "SignalProcessing`Support`SigProc`",
	      "SignalProcessing`Support`SupCode`" ]


If [ TrueQ[ $VersionNumber >= 2.0 ],
     Off[ General::spell ];
     Off[ General::spell1 ] ];


(*  U S A G E     I N F O R M A T I O N  *)

ASPAnalyze::usage =
	"ASPAnalyze[f, t] and ASPAnalyze[f, t, start, end] \
	will plot f(t) as a one-dimensional, continuous-time function \
	(real part shown as solid lines, imaginary part as dashed lines). \
	It will also print the strip of convergence, indicate stability \
	criteria, display the pole-zero diagram, and plot the magnitude \
	and phase responses. \
	For the two-dimensional version, t is a list of two variables \
	(like {t1, t2}) and start/end are optional two-element lists \
	specifying the range of the time-domain plot. \
	All non-variable symbols will be assigned a value of 1 when the \
	analyzer needs to generate a plot (override this by using options \
	like a -> 2)."

(*  E N D     U S A G E     I N F O R M A T I O N  *)


Begin["`Private`"]


(*  M E S S A G E S  *)

LaPlace::notvalid =
	"The forward bilateral LaPlace transform could not be found."
ASPAnalyze::notinteresting =
	"Could not determine the important section of the frequency response."
ASPAnalyze::hasval =
	"The global variable '' has value so it could not be used \
	as a transform variable; '' was used instead."
CTFTransform::notvalid = "The forward Fourier transform could not be found."


(*  ASPAnalyzeQ  *)
ASPAnalyzeQ[start_, end_] :=
	NumberQ[N[start]] && NumberQ[N[end]] && N[end >= start]

(*  checkvar  *)
SetAttributes[checkvar, {HoldFirst}]
checkvar[ xsym_, xval_, str_ ] := xsym /; SameQ[xsym, xval]
checkvar[ xsym_, xval_, str_ ] :=
	Block [ {sym},
		sym = Unique["s"];
		Message[ ASPAnalyze::hasval, str, sym ];
		sym ]

(*  FourierExtent  *)
FourierExtent[freqresp_, defaultvalues_, w_, mag_] :=
	Block [	{newmag, wmax, wmin},
		newmag = mag;
		{wmin, wmax} = Extent1D[ freqresp /. defaultvalues, w ];
		Which [ SameQ[wmin, -Infinity] && SameQ[wmax, Infinity],
			  wmin = wmax = 0,
			SameQ[wmin, -Infinity],
			  wmin = wmax / 100,
			SameQ[wmax, Infinity],
			  wmax = 100 wmin,
			True,
			  newmag = Linear ];
		{wmin, wmax, newmag} ]

(*  LtoFreqResp  *)
LtoFreqResp[ funct_, ltrans_, defaultvalues_,
	     onedflag_, polezeroflag_, lrules_ ] :=
	Block [ {lfreqresp, plotrange, polelist = {0}, rm, rp, stable},

		(*  Print the signal and its Laplace transform  *)
		Print[ funct ];
		Print[ "has the following Laplace transform:" ];
		Print[ TheFunction[ltrans] ];

		(*  Print the Region of Convergence  *)
		Print[ "The region of convergence is:"];
		rm = GetRMinus[ltrans];
		rp = GetRPlus[ltrans];
		If [ onedflag,
		     Print[ rm, " < Re(s) < ", rp ],
		     Print[ rm[[1]], " < Re(s1) < ", rp[[1]] ];
		     Print[ rm[[2]], " < Re(s2) < ", rp[[2]] ] ];

		(*  Print stability information  *)
		stable = PrintStability[ltrans];

		(*  From now on, the default values are used  *)
		If [ ! EmptyQ[defaultvalues],
		     Print[ "The default values are now being considered." ] ];

		(*  Show the Pole-Zero Plot  *)
		If [ polezeroflag,
		     polelist = PoleZeroPlot[ltrans /. defaultvalues] ];

		lfreqresp = TheFunction[ltrans] /. lrules;

		If [ stable /. defaultvalues,
		     Print[ "Since the signal is stable, the" ];
		     Print[ "frequency response can be computed" ];
		     Print[ "directly from the Laplace transform." ];
		     plotrange = All,

		     Print[ "Since the signal is not stable, the" ];
		     Print[ "frequency response has no meaning, but it" ];
		     Print[ "will be plotted anyway.  However, the DC" ];
		     Print[ "(zero-frequency) term will be Infinity which" ];
		     Print[ "will generate a warning from Mathematica." ];
		     plotrange = Automatic,

		     Print[ "Even though the stability of the signal" ];
		     Print[ "is not known, the frequency response will" ];
		     Print[ "still be plotted." ];
		     plotange = Automatic ];

		{ lfreqresp, plotrange, polelist } ]

(*  formatting  *)
Format[ w ]  := "w"
Format[ w1 ] := "w1"
Format[ w2 ] := "w2"

Format[ s ]  := "s"
Format[ s1 ] := "s1"
Format[ s2 ] := "s2"


(*  PrintDefaults  *)
PrintDefaults[oplist_, varlist_, defaultvalues_] :=
	Block [	{},
		If [ EmptyQ[oplist],
		     Print[ "For plotting only, these symbols will" ];
		     Print[ "be assigned a value of 1:  ", varlist ],
		     Print[ "For plotting only, these symbols will" ];
		     Print[ "will be assigned default values:" ];
		     Print[ defaultvalues ] ] ]

(*  PrintStability  *)
PrintStability[ltrans_] :=
	Block [	{stable},
		stable = Stable[ltrans];
		Which [ SameQ[stable, True],
			  Print[ "The system is stable." ],
			SameQ[stable, False],
			  Print[ "The system is not stable." ],
			True,
			  Print[ "The system is stable if ", stable ] ];
		stable ]


(*  A S P A n a l y z e  *)

ASPAnalyze[f_, t_Symbol] := ASPAnalyze[f, t, -20]
ASPAnalyze[f_, t_Symbol, start_] := ASPAnalyze[f, t, start, start + 40]

ASPAnalyze[f_, t_Symbol, start_, end_, op___] :=
	Block [	{bogus, complexpoles, ctft, defaultvalues = {}, freqresp,
		fnumeric, ftemp, funct, ltrans, mag, oplist, plotrange = All,
		polezeroflag, polelist, stable, varlist, wmax, wmin},

		funct = ToContinuous[f];
		oplist = ToList[op];

		(*  CONTINUOUS-TIME DOMAIN ANALYSIS  *)

		(*  Convert expression to plottable/functional form    *)
		(*  and replace constants like Pi with numerical value *)

		fnumeric = N[ TheFunction[funct] ];    

		(*  Find all valueless symbols besides the time variable *)
		(*  The first parameter in Summation is a local symbol   *) 

		ftemp = fnumeric /.
			  ( Summation[n_, l_, u_, inc_][expr_] :>
			    bogus[l, u, inc, GetVariables[expr /. n -> l]] );
		varlist = Select[ GetVariables[ftemp /. oplist],
				  Function[x, ! SameQ[x,t]] ];
		If [ ! EmptyQ[varlist] || ! EmptyQ[oplist],
		     defaultvalues = oplist ~Join~ Map[(#1 -> 1)&, varlist];
		     PrintDefaults[ oplist, varlist, defaultvalues ];
		     fnumeric = fnumeric /. defaultvalues ];

		(*  Plot the signal in the time-domain  *)

		Print[ "Real part of the function is shown as solid lines.\n\
		    Imaginary part of the function is shown as dashed lines."];

		polezeroflag = ( ! SameQ[fnumeric, 0] );
		SignalPlot[fnumeric, {t, start, end},
			   PlotLabel -> "Continuous-Time Domain Analysis" ];


		(*  LAPLACE-DOMAIN ANALYSIS  *)

		(*    Find F(s); if it exists, print it out, give stability  *)
		(*  information, and display a pole-zero plot.		     *)

		ltrans = LaPlace[funct, t, s];
		If [ ! SameQ[Head[ltrans], LTransData],
		     Message[ LaPlace::notvalid ];
		     ctft = CTFTransform[funct, t, w];
		     If [ ! SameQ[Head[ctft], CTFTData],
			  Message[CTFTransform::notvalid]; Return[ltrans] ];
		     freqresp = TheFunction[ctft];
		     wmin = wmax = 0;
		     mag = Linear,

		     { freqresp, plotrange, polelist } =
			LtoFreqResp[ funct, ltrans, defaultvalues,
				     True, polezeroflag, ( s -> I w ) ];
		     complexpoles = Map[Im, polelist];
		     wmin = Min[ 0, Abs[complexpoles] ] / 10;
		     wmax = Max[ 0, Abs[complexpoles] ] 10;
		     mag = Log ];

		(*  FREQUENCY-DOMAIN ANALYSIS  *)

		(*  Plot the interesting section of the freq. response  *)
		(*  That is, plot the stuff around the break points     *)

		Print[ funct ];
		Print[ "has the following frequency response:" ];
		Print[ freqresp ];

		If [ wmin == 0 && wmax == 0,
		     {wmin, wmax, mag} =
		         FourierExtent[freqresp, defaultvalues, w, mag] ];
		If [ wmin == 0 && wmax == 0,
		     Message[ ASPAnalyze::notinteresting ] ];
		If [ SameQ[wmax, 0], wmax = 10 ];
		If [ SameQ[wmin, 0], wmin = wmax/100 ];
		MagPhasePlot[ freqresp /. defaultvalues, { w, wmin, wmax },
			      Domain -> Continuous, DomainScale -> Log,
			      MagRangeScale -> mag, PhaseRangeScale -> Degree,
			      PlotRange -> plotrange ];

		(*  Return the Laplace transform  *)

		ltrans /. s -> checkvar[Global`s, Global`s, "s"] ] /;

	ASPAnalyzeQ[start, end]


ASPAnalyze[f_, {t1_Symbol, t2_Symbol}] := ASPAnalyze[f, {t1, t2}, {-10, -10}]
ASPAnalyze[f_, {t1_Symbol, t2_Symbol}, start_] :=
	ASPAnalyze[f, {t1, t2}, start, start + {20, 20}]

ASPAnalyze[f_, {t1_Symbol, t2_Symbol}, start_, end_, op___] :=
	Block [	{complexpoles, ctft, defaultvalues = {}, deltafuns, freqresp,
		 fnumeric, ftemp, funct, ltrans, mag, oplist, plotrange = All,
		 polelist = {0}, polezeroflag, slist, stable,
		 tlist, varlist, wmax, wmin},

		oplist = ToList[op];
		tlist = { t1, t2 };
		slist = { s1, s2 };
		funct = ToContinuous[f];

		(*  CONTINUOUS-TIME DOMAIN ANALYSIS  *)

		(*  Convert expression to plottable/functional form    *)
		(*  and replace constants like Pi with numerical value *)

		fnumeric = N[ TheFunction[funct] ];    

		(*  Find all valueless symbols besides the time variables *)
		(*  The first parameter in Summation is a local symbol    *) 

		ftemp = fnumeric /.
			  ( Summation[n_, l_, u_, inc_][expr_] :>
			    bogus[l, u, inc, GetVariables[expr /. n -> l]] );
		varlist = Select[GetVariables[ftemp /. oplist],
				 Function[x, ! SameQ[x,t1] && ! SameQ[x,t2]]];
		If [ ! EmptyQ[varlist] || ! EmptyQ[oplist],
		     defaultvalues = oplist ~Join~ Map[(#1 -> 1)&, varlist];
		     PrintDefaults[oplist, varlist, defaultvalues];
		     fnumeric = fnumeric /. defaultvalues ];

		(*  Plot delta functions separately from rest of function  *)

		polezeroflag = ( ! SameQ[fnumeric, 0] );
		SignalPlot[fnumeric,
			   {t1, start[[1]], end[[1]]},
			   {t2, start[[2]], end[[2]]},
			   PlotLabel -> "Continuous-Time Domain Analysis" ];

		(*  LAPLACE-DOMAIN ANALYSIS  *)

		(*  Find F(s) and print it out.  *)

		ltrans = LaPlace[funct, tlist, slist];
		If [ ! SameQ[Head[ltrans], LTransData],
		     Message[ LaPlace::notvalid ];
		     ctft = CTFTransform[funct, tlist, {w1, w2}];
		     If [ ! SameQ[Head[ctft], CTFTData],
			  Message[CTFTransform::notvalid]; Return[ltrans] ];
		     freqresp = TheFunction[ctft];
		     mag = Linear,

		     { freqresp, plotrange, polelist } =
			LtoFreqResp[ funct, ltrans, defaultvalues,
				     False, polezeroflag,
				     { s1 -> I w1, s2 -> I w2 } ];
		     mag = Log ];

		(*  FREQUENCY-DOMAIN ANALYSIS  *)

		(*  Plot the interesting section of the freq. response  *)
		(*  That is, plot the stuff around the break points     *)
		(*  This is only possible for separable transforms.	*)

		Print[ funct ];
		Print[ "has the following frequency response:" ];
		Print[ freqresp ];

		complexpoles = Map[Im, Select[polelist, NumberQ]];
		wmin = Min[ 0, Abs[complexpoles] ] / 10;
		wmax = Max[ 0, Abs[complexpoles] ] 10;
		If [ wmin == 0 && wmax == 0,
		     Message[ ASPAnalyze::notinteresting ] ];
		If [ SameQ[wmax, 0], wmax = 10 ];
		If [ SameQ[wmin, 0], wmin = 1/10 ];

		MagPhasePlot[ freqresp /. defaultvalues,
			      {w1, wmin, wmax}, {w2, wmin, wmax},
			      Domain -> Continuous, DomainScale -> Log,
			      MagRangeScale -> mag, PhaseRangeScale -> Degree,
			      PlotRange -> plotrange ];

		(*  Return the Laplace transform  *)

		ltrans /. { s1 -> checkvar[Global`s1, Global`s1, "s1"],
			    s2 -> checkvar[Global`s2, Global`s2, "s2"] } ] /;

	ASPAnalyzeQ[ start[[1]], end[[1]] ] &&
	    ASPAnalyzeQ[ start[[2]], end[[2]] ]


(*  A u t o c o r r e l a t i o n  *)



(*  E N D     P A C K A G E  *)

End[]
EndPackage[]

If [ TrueQ[ $VersionNumber >= 2.0 ],
     On[ General::spell ];
     On[ General::spell1 ] ];


(*  H E L P     I N F O R M A T I O N  *)

Combine[SPfunctions, { ASPAnalyze } ]
Protect[ASPAnalyze]


(*  E N D I N G     M E S S A G E  *)

Print[ "The analog signal analyzer is now loaded." ]
Null
